/*
moxl - Modular XML Library
Copyright (c) 2007, Malcolm Sparks

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package moxl.parser.model;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import moxl.parser.markup.Markup;
import moxl.parser.markup.types.CharacterDataMarkup;
import moxl.parser.markup.types.CloseElementTagMarkup;
import moxl.parser.markup.types.CommentMarkup;
import moxl.parser.markup.types.EndOfDocumentMarkup;
import moxl.parser.markup.types.OpenElementTagMarkup;
import moxl.raw.RawNode;

public class DocumentBuilder
{
	ParsedDocument document;

	OpenElementTagMarkupStack openElementTagMarkupStack = new OpenElementTagMarkupStack();
	LinkedList<List<RawNode>> childrenStack = new LinkedList<List<RawNode>>();

	enum State {
		PREAMBLE, DOC_ELEMENT, POSTAMBLE, FINISHED;
	}

	State mode = State.PREAMBLE;

	List<RawNode> preamble = new ArrayList<RawNode>();
	List<RawNode> postamble = new ArrayList<RawNode>();

	public DocumentBuilder(ParsedDocument document)
	{
		this.document = document;
	}

	/**
	 * Add markup, one by one. We can be guaranteed that the incoming markup is well-formed.
	 * 
	 * @param markup
	 */
	public void addMarkup(Markup markup)
	{
		if (markup instanceof OpenElementTagMarkup)
		{
			document.setPreamble(preamble);
			mode = State.DOC_ELEMENT;
			processOpenTagMarkup((OpenElementTagMarkup) markup);
		}
		else if (markup instanceof CloseElementTagMarkup)
		{
			processCloseTagMarkup((CloseElementTagMarkup) markup);
		}
		else if (markup instanceof CharacterDataMarkup)
		{
			addChildNode(new ParsedCharacterData((CharacterDataMarkup) markup));
		}
		else if (markup instanceof CommentMarkup)
		{
			processCommentMarkup(markup);
		}
		else if (markup instanceof EndOfDocumentMarkup)
		{
			document.setPostamble(postamble);
		}
	}

	private void processCommentMarkup(Markup markup)
	{
		ParsedComment comment = new ParsedComment((CommentMarkup) markup);
		switch (mode)
		{
		case PREAMBLE:
			preamble.add(comment);
			break;
		case DOC_ELEMENT:
			addChildNode(comment);
			break;
		case POSTAMBLE:
			postamble.add(comment);
			break;
		default:
			throw new IllegalStateException();
		}
	}

	private void addChildNode(RawNode node)
	{
		getCurrentChildList().add(node);
	}

	private void processOpenTagMarkup(OpenElementTagMarkup markup)
	{
		if (markup.isEmpty())
		{
			String xmlns = markup.getAttributeValue("xmlns");
			if (xmlns==null) {
				xmlns = openElementTagMarkupStack.getDeepestNamespace();
			}
			ParsedElement element = new ParsedEmptyElement(document, markup, xmlns);
			if (!childrenStack.isEmpty())
			{
				getCurrentChildList().add(element);
			}
			else
			{
				setDocumentElement(element);
			}
		}
		else
		{
			openElementTagMarkupStack.addLast(markup);
			childrenStack.addLast(new ArrayList<RawNode>());
		}
	}

	private void processCloseTagMarkup(CloseElementTagMarkup closeMarkup)
	{
		OpenElementTagMarkup openMarkup = (OpenElementTagMarkup) openElementTagMarkupStack.removeLast();
		if (openMarkup.getTagName().equals(closeMarkup.getTagName()))
		{
			List<RawNode> children = (List<RawNode>) childrenStack.removeLast();
			String namespace = openElementTagMarkupStack.getDeepestNamespace();
			String localNamespace = openMarkup.getAttributeValue("xmlns");
			ParsedElement element = new ParsedElement(document, openMarkup, children, closeMarkup, localNamespace !=null ? localNamespace : namespace);
			if (childrenStack.isEmpty())
			{
				setDocumentElement(element);
			}
			else
			{
				addChildNode(element);
			}
		}
	}

	private List<RawNode> getCurrentChildList()
	{
		return childrenStack.getLast();
	}

	private void setDocumentElement(ParsedElement element)
	{
		document.setDocumentElement(element);
		mode = State.POSTAMBLE;
	}

}
